import { IdleValue } from './async';
import { Graph } from './graph';
import { ServiceCollection } from './serviceCollection';
import { getServiceDependencies, IInstantiationService, optional, ServiceIdentifier, ServicesAccessor } from './instantiation';
import { SyncDescriptor } from './descriptors';

// 周期依赖错误
class CyclicDependencyError extends Error {
	constructor(graph: Graph<any>) {
		super('cyclic dependency between services');
		this.message = graph.toString();
	}
}

// 实例化服务
export class InstantiationService implements IInstantiationService {

	// 服务容器
	private readonly _services: ServiceCollection;
	// 严格模式
	private readonly _strict: boolean;
	// 父级实例化服务
	private readonly _parent?: InstantiationService;

	constructor(services: ServiceCollection = new ServiceCollection(), strict: boolean = false, parent?: InstantiationService) {
		this._services = services;
		this._strict = strict;
		this._parent = parent;

		// TODO：把自己本身注入到DI容器当中
		this._services.set(IInstantiationService, this);
	}

	// 为什么要创建父子层级DI容器
	createChild(services: ServiceCollection): IInstantiationService {
		return new InstantiationService(services, this._strict, this);
	}

	// 函数调用，通过 ServicesAccessor 回调函数能够 get 访问到 ioc 容器
	// TODO：这个函数究竟发生了什么
	invokeFunction<R, TS extends any[] = []>(fn: (accessor: ServicesAccessor, ...args: TS) => R, ...args: TS): R {
		// let _trace = Trace.traceInvocation(fn);
		let _done = false;
		try {
			const accessor: ServicesAccessor = {
				get: <T>(id: ServiceIdentifier<T>, isOptional?: typeof optional) => {

					if (_done) {
						console.error('service accessor is only valid during the invocation of its target method');
					}

					// const result = this._getOrCreateServiceInstance(id, _trace);
					const result = this._getOrCreateServiceInstance(id);
					if (!result && isOptional !== optional) {
						throw new Error(`[invokeFunction] unknown service '${id}'`);
					}
					return result;
				}
			};
			return fn(accessor, ...args);
		} finally {
			_done = true;
			// _trace.stop();
		}
	}

	// 创建实例
	createInstance(ctorOrDescriptor: any | SyncDescriptor<any>, ...rest: any[]): any {
		// let _trace: Trace;
		let result: any;

		if (ctorOrDescriptor instanceof SyncDescriptor) {
			// 如果是包含依赖服务的服务
			
			// _trace = Trace.traceCreation(ctorOrDescriptor.ctor);
			// result = this._createInstance(ctorOrDescriptor.ctor, ctorOrDescriptor.staticArguments.concat(rest), _trace);
			result = this._createInstance(ctorOrDescriptor.ctor, ctorOrDescriptor.staticArguments.concat(rest));
		} else {
			// 如果是不包含依赖服务的服务

			// _trace = Trace.traceCreation(ctorOrDescriptor);
			// result = this._createInstance(ctorOrDescriptor, rest, _trace);
			result = this._createInstance(ctorOrDescriptor, rest);
		}
		// _trace.stop();
		return result;
	}

	// private _createInstance<T>(ctor: any, args: any[] = [], _trace: Trace): T {
	private _createInstance<T>(ctor: any, args: any[] = []): T {

		// arguments defined by service decorators
		let serviceDependencies = getServiceDependencies(ctor).sort((a: any, b: any) => a.index - b.index);
		
		let serviceArgs: any[] = [];
		for (const dependency of serviceDependencies) {
			// let service = this._getOrCreateServiceInstance(dependency.id, _trace);
			let service = this._getOrCreateServiceInstance(dependency.id);
			if (!service && this._strict && !dependency.optional) {
				throw new Error(`[createInstance] ${ctor.name} depends on UNKNOWN service ${dependency.id}.`);
			}
			serviceArgs.push(service);
		}

		let firstServiceArgPos = serviceDependencies.length > 0 ? serviceDependencies[0].index : args.length;

		// check for argument mismatches, adjust static args if needed
		if (args.length !== firstServiceArgPos) {
			console.warn(`[createInstance] First service dependency of ${ctor.name} at position ${firstServiceArgPos + 1} conflicts with ${args.length} static arguments`);

			let delta = firstServiceArgPos - args.length;
			if (delta > 0) {
				// 初始化时，可以先不管非ioc当中的实例。学到一招
				args = args.concat(new Array(delta));
			} else {
				args = args.slice(0, firstServiceArgPos);
			}
		}

		// now create the instance
		return <T>new ctor(...[...args, ...serviceArgs]);
	}

	private _setServiceInstance<T>(id: ServiceIdentifier<T>, instance: T): void {
		if (this._services.get(id) instanceof SyncDescriptor) {
			this._services.set(id, instance);
		} else if (this._parent) {
			this._parent._setServiceInstance(id, instance);
		} else {
			throw new Error('illegalState - setting UNKNOWN service instance');
		}
	}

	private _getServiceInstanceOrDescriptor<T>(id: ServiceIdentifier<T>): T | SyncDescriptor<T> {
		let instanceOrDesc = this._services.get(id);
		if (!instanceOrDesc && this._parent) {
			return this._parent._getServiceInstanceOrDescriptor(id);
		} else {
			return instanceOrDesc;
		}
	}

	// private _getOrCreateServiceInstance<T>(id: ServiceIdentifier<T>, _trace: Trace): T {
	private _getOrCreateServiceInstance<T>(id: ServiceIdentifier<T>): T {
		let thing = this._getServiceInstanceOrDescriptor(id);
		if (thing instanceof SyncDescriptor) {
			// return this._createAndCacheServiceInstance(id, thing, _trace.branch(id, true));
			return this._createAndCacheServiceInstance(id, thing);
		} else {
			// _trace.branch(id, false);
			return thing;
		}
	}

	// private _createAndCacheServiceInstance<T>(id: ServiceIdentifier<T>, desc: SyncDescriptor<T>, _trace: Trace): T {
	private _createAndCacheServiceInstance<T>(id: ServiceIdentifier<T>, desc: SyncDescriptor<T>): T {
		// 组合（三重）
		// type Triple = { id: ServiceIdentifier<any>, desc: SyncDescriptor<any>, _trace: Trace };
		type Triple = { id: ServiceIdentifier<any>, desc: SyncDescriptor<any> };
		const graph = new Graph<Triple>(data => data.id.toString());

		let cycleCount = 0;
		// const stack = [{ id, desc, _trace }];
		const stack = [{ id, desc }];
		while (stack.length) {
			const item = stack.pop()!;

			// TODO: 待处理
			graph.lookupOrInsertNode(item);

			// a weak but working heuristic for cycle checks
			if (cycleCount++ > 1000) {
				throw new CyclicDependencyError(graph);
			}

			// check all dependencies for existence and if they need to be created first
			for (let dependency of getServiceDependencies(item.desc.ctor)) {

				let instanceOrDesc = this._getServiceInstanceOrDescriptor(dependency.id);
				if (!instanceOrDesc && !dependency.optional) {
					console.warn(`[createInstance] ${id} depends on ${dependency.id} which is NOT registered.`);
				}

				if (instanceOrDesc instanceof SyncDescriptor) {
					// const d = { id: dependency.id, desc: instanceOrDesc, _trace: item._trace.branch(dependency.id, true) };
					const d = { id: dependency.id, desc: instanceOrDesc };
					graph.insertEdge(item, d);
					stack.push(d);
				}
			}
		}

		while (true) {
			const roots = graph.roots();

			// if there is no more roots but still
			// nodes in the graph we have a cycle
			if (roots.length === 0) {
				if (!graph.isEmpty()) {
					throw new CyclicDependencyError(graph);
				}
				break;
			}

			for (const { data } of roots) {
				// Repeat the check for this still being a service sync descriptor. That's because
				// instantiating a dependency might have side-effect and recursively trigger instantiation
				// so that some dependencies are now fullfilled already.
				const instanceOrDesc = this._getServiceInstanceOrDescriptor(data.id);
				if (instanceOrDesc instanceof SyncDescriptor) {
					// create instance and overwrite the service collections
					// const instance = this._createServiceInstanceWithOwner(data.id, data.desc.ctor, data.desc.staticArguments, data.desc.supportsDelayedInstantiation, data._trace);
					const instance = this._createServiceInstanceWithOwner(data.id, data.desc.ctor, data.desc.staticArguments, data.desc.supportsDelayedInstantiation);
					this._setServiceInstance(data.id, instance);
				}
				graph.removeNode(data);
			}
		}

		return <T>this._getServiceInstanceOrDescriptor(id);
	}

	// private _createServiceInstanceWithOwner<T>(id: ServiceIdentifier<T>, ctor: any, args: any[] = [], supportsDelayedInstantiation: boolean, _trace: Trace): T {
	private _createServiceInstanceWithOwner<T>(id: ServiceIdentifier<T>, ctor: any, args: any[] = [], supportsDelayedInstantiation: boolean): T {
		if (this._services.get(id) instanceof SyncDescriptor) {
			// return this._createServiceInstance(ctor, args, supportsDelayedInstantiation, _trace);
			return this._createServiceInstance(ctor, args, supportsDelayedInstantiation);
		} else if (this._parent) {
			// return this._parent._createServiceInstanceWithOwner(id, ctor, args, supportsDelayedInstantiation, _trace);
			return this._parent._createServiceInstanceWithOwner(id, ctor, args, supportsDelayedInstantiation);
		} else {
			throw new Error(`illegalState - creating UNKNOWN service instance ${ctor.name}`);
		}
	}

	// private _createServiceInstance<T>(ctor: any, args: any[] = [], _supportsDelayedInstantiation: boolean, _trace: Trace): T {
	private _createServiceInstance<T>(ctor: any, args: any[] = [], _supportsDelayedInstantiation: boolean): T {
		if (!_supportsDelayedInstantiation) {
			// eager instantiation
			// return this._createInstance(ctor, args, _trace);
			return this._createInstance(ctor, args);

		} else {
			// Return a proxy object that's backed by an idle value. That
			// strategy is to instantiate services in our idle time or when actually
			// needed but not when injected into a consumer
			// const idle = new IdleValue<any>(() => this._createInstance<T>(ctor, args, _trace));
			const idle = new IdleValue<any>(() => this._createInstance<T>(ctor, args));
			return <T>new Proxy(Object.create(null), {
				get(target: any, key: PropertyKey): any {
					if (key in target) {
						return target[key];
					}
					let obj = idle.value;
					let prop = obj[key];
					if (typeof prop !== 'function') {
						return prop;
					}
					prop = prop.bind(obj);
					target[key] = prop;
					return prop;
				},
				set(_target: T, p: PropertyKey, value: any): boolean {
					idle.value[p] = value;
					return true;
				}
			});
		}
	}
}
